#ifndef __GENETIC_ALGORITHM_H__
#define __GENETIC_ALGORITHM_H__

#include <vector>
#include "genetic_utils.h"

template<class T>
class genome: public std::vector<T> /** chromosome (consist of a number of genes) */
{
public:
	double mFitness = 0.0; /** the score of fitness */
};


template<class T>
class geneticAlg
{
public:
	geneticAlg(double crossoverRate, double mutationRate)
		:mCrossoverRate(crossoverRate),
		mMutationRate(mutationRate)
	{
	}
	virtual ~geneticAlg(){}

public:
	virtual void epoch(std::vector<genome<T>>& oldPop, std::vector<genome<T>>& newPop)
	{
		double totalFitness = getTotalFitness(oldPop);
		size_t popSize = oldPop.size();
		//now we enter the GA loop

		//repeat until a new population is generated
		while (newPop.size() < popSize)
		{
			//grab two chromosomes
			genome<T> mum = parentSelection(oldPop, totalFitness);
			genome<T> dad = parentSelection(oldPop, totalFitness);

			//create some offspring via crossover
			genome<T>	baby1, baby2;

			crossover(mum, dad, baby1, baby2);

			//now we mutate
			mutate(baby1);
			mutate(baby2);

			//now copy into newPop population
			newPop.push_back(baby1);
			newPop.push_back(baby2);
		}
	}

protected:
	virtual genome<T> parentSelection(std::vector<genome<T>>& pop, double totalFitness)
	{
		//generate a random number between 0 & total fitness count
		double Slice = (double)(RandFloat() * totalFitness);

		//this will be set to the chosen chromosome
		genome<T> theChosenOne;

		//go through the chromosomes adding up the fitness so far
		double fitnessSoFar = 0;

		for (size_t i = 0; i < pop.size(); ++i)
		{
			fitnessSoFar += pop[i].mFitness;

			//if the fitness so far > random number return the chromo at 
			//this point
			if (fitnessSoFar >= Slice)
			{
				theChosenOne = pop[i];

				break;
			}
		}

		return theChosenOne;
	}

	virtual void crossover(const genome<T>& mum, const genome<T>& dad, genome<T> & baby1, genome<T>& baby2)
	{
		//just return parents as offspring dependent on the rate
		//or if parents are the same
		if ((RandFloat() > mCrossoverRate) || (mum == dad))
		{
			baby1 = mum;
			baby2 = dad;
			return;
		}

		//determine a crossover point
		int cp = RandInt(0, (int)mum.size() - 1);

		//create the offspring
		for (int i = 0; i < cp; ++i)
		{
			baby1.push_back(mum[i]);
			baby2.push_back(dad[i]);
		}

		for (size_t i = cp; i < mum.size(); ++i)
		{
			baby1.push_back(dad[i]);
			baby2.push_back(mum[i]);
		}
	}

	virtual void mutate(genome<T>& chromo) = 0;
#if 0
	{
		//traverse the chromosome and mutate each weight dependent
		//on the mutation rate
		for (size_t i = 0; i < chromo.size(); ++i)
		{
			//do we perturb this weight?
			if (RandFloat() < mMutationRate)
			{
				chromo[i] += RandomClamped();
			}
		}
	}
#endif
	virtual inline double getTotalFitness(std::vector<genome<T>>& pop)
	{
		double totalFitness = 0.0;

		for (size_t i = 0; i < pop.size(); i++)
		{
			totalFitness += pop[i].mFitness;
		}

		return totalFitness;
	}
protected:
	double mCrossoverRate;
	double mMutationRate;
};


#endif // __GENETIC_ALGORITHM_H__
